/// 缩略图中间件
module jinji.middleware.thumb;

import jinji.common.type,
jinji.config,
jinji.util.error,
std.array,
tame.string;

/// 缩略图选项
enum options = split("-vf scale=w='min(iw,320)':h='min(ih,240)':force_original_aspect_ratio=decrease," ~
			"pad=320:240:(ow-iw)/2:(oh-ih)/2:white -q:v 90 -y -v warning -s", ' ');

/++
生成缩略图
Params:
	url = 图片地址
	size = 缩略图尺寸
	format = 缩略图格式
Return:
	缩略图文件
Throws:
	Exception
+/
auto getThumb(in char[] url, in char[] size, in char[] format) {
	import jinji.util.io,
	tame.buffer,
	tame.conv;

	//import tame.io.file;
	import std.stdio;

	const isFile = url.startsWith("file:///");
	auto file = isFile ? File(url[8 .. $], "rb") : stdin;
	// 如果是文件且ffmpeg未安装或未指定缩略图尺寸，则直接返回文件
	if (isFile && ("ffmpeg" !in C.programs || !size.length))
		return file;

	Sink!(const(char)[]) cmd;
	cmd ~= C.programs["ffmpeg"];
	cmd ~= "-rw_timeout";
	char[11] buf = void;
	cmd ~= buf[0 .. intToStr(buf.ptr, C.timeout * 1000UL)];
	cmd ~= "-i";
	cmd ~= isFile ? "-" : url;
	cmd ~= options;
	cmd ~= size;
	cmd ~= "-f";
	cmd ~= format;
	if (C.thumbDir.length) {
		// FIXME: 仅支持绝对路径
		StringSink s;
		s ~= C.thumbDir;
		s ~= '\\';
		s ~= url[8 .. $];
		s ~= '.';
		s ~= format;
		const path = cast(Path)s[];
		try {
			auto thumb = File(path);
			if (thumb.size > 0)
				return thumb;
		} catch (Exception e) {
		}
		cmd ~= path;
	} else
		cmd ~= "-";
	return spawn(cmd, file, null, true);
}

version (App)  : import dast.ws;

/// 处理缩略图请求，格式：/thumb/{pid}/{width}x{height}.{ext}
void getThumb(WSServer, WSClient client, in Request req, scope NextHandler next) {
	import dast.net.mimetypes,
	jinji.db,
	jinji.link.model,
	jinji.middleware.web,
	std.uri;

	auto status = Status.OK;
	const path = req.path;
	if (!path.startsWith("/thumb/"))
		return next();

	auto url = path[7 .. $];
	checkArg(url.stringof, url.length <= maxURLLength, "url too long");
	auto p = url.lastIndexOf('/');
	try {
		checkArg(url.stringof, p != -1, "invalid url");
		const(char)[] file = decodeComponent(url[0 .. p]);
		if (!has!(Link, "url=", file)) {
			client.setHeader(Status.NotFound, "text/plain");
			client.sendChunked(Status.NotFound);
			return;
		}
		url = url[p + 1 .. $];
		p = url.indexOf('.');
		const(char)[] type = p == -1 ? null : getMimeType(url[p .. $]);
		if (!type.length) {
			const i = file.lastIndexOf('.');
			if (i != -1)
				type = getMimeType(file[i .. $]);
		}
		client.setHeader(status, type);
		if (type.startsWith("image/"))
			type = type[6 .. $];
		auto thumb = getThumb(file, p == -1 ? [] : url[0 .. p], type);
		ubyte[1024] buf = void;
		while (true) {
			const data = thumb.rawRead(buf);
			if (!data.length)
				break;
			client.sendChunked(data);
		}
	} catch (Exception e) {
		client.setHeader(Status.Error, "text/plain");
		client.sendChunked(e.toString());
	}
}

/++
获取请求的MIME类型
Params:
	req = 请求
Return: MIME类型
+/
private const(char)[] getType(in Request req) pure {
	const accept = req.headers["Accept"];
	foreach (a; accept.splitter(',')) {
		if (a.startsWith("image/"))
			return a;
	}
	return null;
}
